#include <mm.h>
#include <mm_copy.h>
#include <vga.h>
#include <string.h>
#include <i386.h>

uint_32 mem_avaible; //memoria disponible

mm_page *idm_table = NULL;

uint_32 pa_page_map[32768]; //mapa de bit con las paginas fisicas libres

void* mm_mem_alloc() {
	
	uint_32 p, page_index, page_bitmap;
	
	for(p = MM_USER_BASE; p < mem_avaible; p += 0x1000) {
		
		page_index = p >> 12;
		page_bitmap = pa_page_map[page_index / 32];
		
		if  (!((1 << (page_index % 32)) & page_bitmap)) {
			page_bitmap |= (1 << (page_index % 32));
			pa_page_map[page_index / 32] = page_bitmap;
			return (void *) p;
		}
	}
	return NULL;
}
void* mm_mem_kalloc() {
	
	uint_32 p, page_index, page_bitmap;
	
	for(p = MM_KERNEL_BASE; p < MM_USER_BASE; p += 0x1000) {
		
		page_index = p >> 12;
		page_bitmap = pa_page_map[page_index / 32];
		
		if  (!((0x1 << (page_index % 32)) & page_bitmap)) {
			page_bitmap |= (0x1 << (page_index % 32));
			pa_page_map[page_index / 32] = page_bitmap;
			return (void *) p;
		}
	}
	return NULL;
}

void mm_mem_free(void* page) {
	uint_32 p, page_index;
	
	p = (uint_32) page;
	page_index = p >> 12;
	pa_page_map[page_index / 32] &= ~(0x1 << (page_index % 32));
}

//mapea la tabla de pagina recibida a los primeros 4m con identity mapping
void mm_ident_map(void *page) {
 	uint_32 addr, i;
	mm_page *table_entry;

	//primero limpio la pagina
	memset(page, 0, PAGE_SIZE);

	addr = 0;
	table_entry = page;
	for (i = 0; i < 1024; i++) {

		//seteo con PRESENTE + R/W + SUPERVISOR
		table_entry->attr = MM_ATTR_P | MM_ATTR_RW | MM_ATTR_US_S;
		table_entry->base = addr >> 12;

		table_entry++;
		addr += 0x1000;
	}
}

mm_page* mm_dir_new(void) {

	mm_page *dir;


	//pido una pagina para el directorio de paginas dentro de la zona del kernel
	dir = mm_mem_kalloc();
	if (!dir) 
		return NULL;

	//limpio la pagina para el directorio
	memset(dir, 0, PAGE_SIZE);

	//finalmente creo en el direcotrio la entrada correspondiente a la primer tabla de pagina 
	dir->base = ((uint_32) idm_table) >> 12;
	dir->attr = MM_ATTR_P | MM_ATTR_RW | MM_ATTR_US_S | MM_ATTR_SZ_4K;
	 
	return dir;

}

void mm_dir_free(mm_page* d) {

	mm_page *dir_entry, *table_entry;
	uint_32 i, j;

	if (NULL == d)
		return;

	//apunto a la segunda entrada del directorio (la primer entrada corresponde a la tabla idm en los priemros 4mb
	//asi que la ignoro
	dir_entry = d + 1;

	
	//itero por el resto de las entradas y libero las tablas en caso de ser necesario
	for (i = 1; i < 1024; i++) {
		if (!mm_entry_is_null(dir_entry)) { 
			//si habia una entrada en el directorio entonces recorro la tabla de paginas asociada
			table_entry = (mm_page *) (dir_entry->base << 12);
			for (j = 0; j < 1024; j++) {
				mm_copy_page_entry_free(table_entry, (void *) ((i * 1024 + j) << 12) );
				table_entry++;
			}
			//libero la pagina asociada a la tabla de paginas
			mm_mem_free((void *) (dir_entry->base << 12));
		}
		dir_entry++;
	}
	
	//libero la pagina asociada al directorio de paginas
	mm_mem_free(d);
}

//esta funcion calcula la cantidad de memoria disponible
void mm_mem_detect(){
	char *mem_limit;

	mem_limit =(char *) 0x100000; //arranco desde el primer mega

	while(mem_limit < ((char *) 0xFFFFFFFF)) {
		*mem_limit = 0x10;
		if ((*mem_limit) != 0x10) 
			break;
		mem_limit += 0x1000;
	}

	mem_avaible = (uint_32) mem_limit;
}

//esta funcion me devuelve la entrada en la tabla de pagina correspondiente a una direccion virtual
//en caso de ser necesario se crea la tabla de paginas y las entradas necesarias en el directorio
//devuelve NULL en caso de error
mm_page * mm_get_vt_addr_entry(void *d, void *addr) {

	mm_page *dir_entry, *table_entry;

	dir_entry = (mm_page *) d;
 	//me muevo a la entrada correspondiente a la direccion
	dir_entry += ((uint_32) addr) >> 22;

	if (mm_entry_is_null(dir_entry)) {
		//si es null tengo que crear la tabla
		//pido una pagina fisica
		table_entry = mm_mem_kalloc();
		if (NULL == table_entry) {
			//si no hay memoria disponible retorno null
			return NULL;
		}
		//seteo la tabla en 0
	 	memset(table_entry, 0, PAGE_SIZE);
		//agrego la entrada en el directorio de paginas
		dir_entry->base = ((uint_32) table_entry) >> 12;
		dir_entry->attr = MM_ATTR_P | MM_ATTR_RW | MM_ATTR_US_U | MM_ATTR_SZ_4K;
	}

	table_entry = (mm_page *)  (dir_entry->base << 12);
	table_entry += (((uint_32) addr) & 0x003FF000) >> 12;

	if (d == (void*)rcr3()) invlpg(addr);

	return table_entry;
}

//esta funcion busca en los tablas de paginas una direccion virutal libre para ser usada.
//devuelve NULL en caso de error 
void * mm_get_free_vt_addr(void *d) {
	mm_page *dir_entry, *table_entry;
	
	uint_32 i, j, ret_dir;
	
	//cargo el directorio de paginas
	dir_entry = d;

	//avanzo a la segunda entrada porque en la primera son los 4mb con idm
	dir_entry++;

	//arranco en 4mb
	ret_dir = 0x400000;

	//recorro el directorio de paginas
	for (i=1; i < 1024; i++) {
		//si la entrada esta libre en el directorio devuelvo esa direccion
		if (mm_entry_is_null(dir_entry)) {
			return (void *) ret_dir;
		}
		else {
			//sino recorro las entradas en la  tabla
			table_entry = (mm_page *)  (dir_entry->base << 12);
			for (j = 0; j < 1024; j++) {
				if (mm_entry_is_null(table_entry)) {
					return (void *) ret_dir;
				}
				table_entry++;
				ret_dir += 0x1000;
			}
		}
		dir_entry++;
	}
	return NULL;
}

void * palloc() {

	//void *ps_page;
	void *vt_page;
	mm_page *table_entry;
 	
	//pido una pagina virtual libre
	vt_page = mm_get_free_vt_addr((void *) rcr3());
	if(NULL == vt_page) {
		return NULL;
	}

	//busco la entrada en la tabla de paginas correspondiente a la pagina virtual
	table_entry = mm_get_vt_addr_entry((void *) rcr3(), vt_page);
	if (NULL == table_entry) {
		return NULL;
	}

	// //pido una nueva pagina en memoria de usuario
	// ps_page = mm_mem_alloc();
	// //si no hay paginas libres retorno null
	// if (NULL == ps_page) {
	// 	return NULL;
	// }

	//table_entry->attr = MM_ATTR_P | MM_ATTR_RW | MM_ATTR_US;
	//table_entry->base = ((uint_32) ps_page) >> 12;
	
	// Asignacion Paja la marco como no presente y le seteo el bit de TBA
	table_entry->attr = MM_ATTR_TBA ;
	

	return vt_page;
}

//esta funcion retorna la memoria total del sistema
uint_32 mm_get_total_mem() {
	return mem_avaible;
}

extern void* _end; // Puntero al fin del c'odigo del kernel.bin (definido por LD).

void mm_init(void) {

	//cuento la memoria disponible
	mm_mem_detect();

	//pongo en cero el bitmap 
	memset(pa_page_map, 0, 32768 * 4);

	//pido una pagina para la tabla de los primero 4mb con idm
	idm_table = mm_mem_kalloc();	
	
	//inicializo la tabla de idm
	mm_ident_map(idm_table);
	
	//seteo la primer pagina de la memoria como no presente tal como se pide 
	idm_table->attr = 0;
	
}

int
mm_share_page( void * vt_addr )
{
	int status = 0;
	mm_page *table_entry;
	table_entry = mm_get_vt_addr_entry((void *) rcr3(), vt_addr);
	
	if (table_entry->attr & MM_ATTR_P &&
		table_entry->attr & MM_ATTR_US_U)
	{
		if (table_entry->attr & MM_ATTR_COW)
			mm_copy_cow_map(table_entry, vt_addr);
		
		if (table_entry->attr & MM_ATTR_RW_W)
			table_entry->attr |= MM_ATTR_SHR;
		
		invlpg(vt_addr);
	}
	else
		status = -1;
		
	return status;
} 
